using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;

namespace Com.PhilChuang.Utils
{
	[Serializable]
	public class VersionInfo
	{
		[SimpleEqualsAndHashcode]
		public string CompanyName
		{ get; private set; }
		
		[SimpleEqualsAndHashcode]
		public string ProductName
		{ get; private set; }
	
		[SimpleEqualsAndHashcode]
		public int VersionMajor
		{ get; private set; }

		[SimpleEqualsAndHashcode]
		public int VersionMinor
		{ get; private set; }

		[SimpleEqualsAndHashcode]
		public int VersionPatch
		{ get; private set; }

		[SimpleEqualsAndHashcode]
		public int VersionBuild
		{ get; private set; }

		[SimpleEqualsAndHashcode]
		public string VersionModifier
		{ get; private set; }

		[SimpleEqualsAndHashcode]
		public DateTime VersionDate
		{ get; private set; }

		public VersionInfo (string companyName, string productName, int versionMajor, int versionMinor, int versionPatch, int versionBuild, string versionModifier, DateTime versionDate)
		{
			CompanyName = companyName;
			ProductName = productName;
			VersionMajor = versionMajor;
			VersionMinor = versionMinor;
			VersionPatch = versionPatch;
			VersionBuild = versionBuild;
			VersionModifier = versionModifier;
			VersionDate = versionDate;
		}

		public string ProductId
		{
			get { return string.Format ("{0} {1} v{2}", CompanyName, ProductName, VersionMajor.ToString ()); }
		}

		/// <summary>
		/// CompanyName, ProductName, VersionString, VersionDate
		/// </summary>
		public string FullProductId
		{
			get
			{
				return string.Format ("{0} {1} v{2} {3}", 
					CompanyName, 
					ProductName, 
					VersionString,
					VersionDate.ToString ("(MM/dd/yyyy)"));
			}
		}

		/// <summary>
		/// Major, Minor
		/// </summary>
		/// <returns></returns>
		public string VersionMajorMinor
		{
			get
			{
				return String.Format ("{0}.{1}",
				                      VersionMajor,
				                      VersionMinor);
			}
		}

		/// <summary>
		/// Major, Minor, Patch, Build, Modifier
		/// </summary>
		/// <returns></returns>
		public string VersionString
		{
			get
			{
				return String.Format ("{0}.{1}.{2}.{3}{4}",
				                      VersionMajor,
				                      VersionMinor,
				                      VersionPatch,
				                      VersionBuild,
				                      !String.IsNullOrEmpty (VersionModifier) ? " " + VersionModifier : String.Empty);
			}
		}

		/// <summary>
		/// ProductName, VersionString
		/// </summary>
		/// <returns></returns>
		public override string ToString ()
		{
			return String.Format ("{0} v{1}",
				ProductName,
				VersionString);
		}

		/// <summary>
		/// Compares version strings
		/// </summary>
		/// <param name="ver1"></param>
		/// <param name="ver2"></param>
		/// <returns value="Less than zero">ver1 is less than ver2</returns>
		/// <returns value="Zero">ver1 is equal to ver2</returns>
		/// <returns value="Greater than zero">ver1 is greater than ver2</returns>
		public static int CompareVersions (String ver1, String ver2)
		{
			ver1.ThrowIfNull ("ver1");
			ver2.ThrowIfNull ("ver2");
			if (ver1 == ver2) return 0;

			var split1 = new List<String> (ver1.Split ('.'));
			var split2 = new List<String> (ver2.Split ('.'));
			var numParts = Math.Max (split1.Count, split2.Count);
			while (split1.Count < numParts)
				split1.Add ("0");
			while (split2.Count < numParts)
				split2.Add ("0");
			
			for (int i = 0; i < numParts; i++)
			{
				var part1 = split1[i];
				var part2 = split2[i];

				// compare as strings right away
				var comp = part1.CompareTo (part2);
				if (comp == 0) continue;

				String qual1 = null, qual2 = null;
				if (i == 3)
				{ // build part might contain qualifier
					// split qualifiers from build
					var idx = IndexOfFirstNonDigit (part1);
					if (idx != -1)
						qual1 = part1.Substring (idx).Trim ();
					idx = IndexOfFirstNonDigit (part2);
					if (idx != -1)
						qual2 = part2.Substring (idx).Trim ();
				}

				int num1, num2;
				if (int.TryParse (part1, out num1)
					&& int.TryParse (part2, out num2))
				{ // both are numbers, compare as ints
					comp = num1.CompareTo (num2);
				}

				if (i == 3 // build part
					&& comp == 0 // numbers are equal
					&& (qual1 != null || qual2 != null)) // has version qualifiers
				{ // compare version qualifiers
					comp = String.Compare (qual1, qual2);
				}

				if (comp != 0) return comp;
			}
			return 0;
		}

		private static int IndexOfFirstNonDigit (IEnumerable<char> s)
		{
			int idx = 0;
			foreach (var ch in s)
			{
				if (!(ch >= '0' && ch <= '9'))
					return idx;
				idx++;
			}
			return -1;
		}
	}
}